/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * This class is used to create operating system processes.
 *
 * <p>Each {@code ProcessBuilder} instance manages a collection
 * of process attributes.  The {@link #start()} method creates a new
 * {@link Process} instance with those attributes.  The {@link
 * #start()} method can be invoked repeatedly from the same instance
 * to create new subprocesses with identical or related attributes.
 *
 * <p>Each process builder manages these process attributes:
 *
 * <ul>
 *
 * <li>a <i>command</i>, a list of strings which signifies the
 * external program file to be invoked and its arguments, if any.
 * Which string lists represent a valid operating system command is
 * system-dependent.  For example, it is common for each conceptual
 * argument to be an element in this list, but there are operating
 * systems where programs are expected to tokenize command line
 * strings themselves - on such a system a Java implementation might
 * require commands to contain exactly two elements.
 *
 * <li>an <i>environment</i>, which is a system-dependent mapping from
 * <i>variables</i> to <i>values</i>.  The initial value is a copy of
 * the environment of the current process (see {@link System#getenv()}).
 *
 * <li>a <i>working directory</i>.  The default value is the current
 * working directory of the current process, usually the directory
 * named by the system property {@code user.dir}.
 *
 * <li><a name="redirect-input">a source of <i>standard input</i></a>.
 * By default, the subprocess reads input from a pipe.  Java code
 * can access this pipe via the output stream returned by
 * {@link Process#getOutputStream()}.  However, standard input may
 * be redirected to another source using
 * {@link #redirectInput(Redirect) redirectInput}.
 * In this case, {@link Process#getOutputStream()} will return a
 * <i>null output stream</i>, for which:
 *
 * <ul>
 * <li>the {@link OutputStream#write(int) write} methods always
 * throw {@code IOException}
 * <li>the {@link OutputStream#close() close} method does nothing
 * </ul>
 *
 * <li><a name="redirect-output">a destination for <i>standard output</i>
 * and <i>standard error</i></a>.  By default, the subprocess writes standard
 * output and standard error to pipes.  Java code can access these pipes
 * via the input streams returned by {@link Process#getInputStream()} and
 * {@link Process#getErrorStream()}.  However, standard output and
 * standard error may be redirected to other destinations using
 * {@link #redirectOutput(Redirect) redirectOutput} and
 * {@link #redirectError(Redirect) redirectError}.
 * In this case, {@link Process#getInputStream()} and/or
 * {@link Process#getErrorStream()} will return a <i>null input
 * stream</i>, for which:
 *
 * <ul>
 * <li>the {@link InputStream#read() read} methods always return
 * {@code -1}
 * <li>the {@link InputStream#available() available} method always returns
 * {@code 0}
 * <li>the {@link InputStream#close() close} method does nothing
 * </ul>
 *
 * <li>a <i>redirectErrorStream</i> property.  Initially, this property
 * is {@code false}, meaning that the standard output and error
 * output of a subprocess are sent to two separate streams, which can
 * be accessed using the {@link Process#getInputStream()} and {@link
 * Process#getErrorStream()} methods.
 *
 * <p>If the value is set to {@code true}, then:
 *
 * <ul>
 * <li>standard error is merged with the standard output and always sent
 * to the same destination (this makes it easier to correlate error
 * messages with the corresponding output)
 * <li>the common destination of standard error and standard output can be
 * redirected using
 * {@link #redirectOutput(Redirect) redirectOutput}
 * <li>any redirection set by the
 * {@link #redirectError(Redirect) redirectError}
 * method is ignored when creating a subprocess
 * <li>the stream returned from {@link Process#getErrorStream()} will
 * always be a <a href="#redirect-output">null input stream</a>
 * </ul>
 *
 * </ul>
 *
 * <p>Modifying a process builder's attributes will affect processes
 * subsequently started by that object's {@link #start()} method, but
 * will never affect previously started processes or the Java process
 * itself.
 *
 * <p>Most error checking is performed by the {@link #start()} method.
 * It is possible to modify the state of an object so that {@link
 * #start()} will fail.  For example, setting the command attribute to
 * an empty list will not throw an exception unless {@link #start()}
 * is invoked.
 *
 * <p><strong>Note that this class is not synchronized.</strong>
 * If multiple threads access a {@code ProcessBuilder} instance
 * concurrently, and at least one of the threads modifies one of the
 * attributes structurally, it <i>must</i> be synchronized externally.
 *
 * <p>Starting a new process which uses the default working directory
 * and environment is easy:
 *
 * <pre> {@code
 * Process p = new ProcessBuilder("myCommand", "myArg").start();
 * }</pre>
 *
 * <p>Here is an example that starts a process with a modified working
 * directory and environment, and redirects standard output and error
 * to be appended to a log file:
 *
 * <pre> {@code
 * ProcessBuilder pb =
 *   new ProcessBuilder("myCommand", "myArg1", "myArg2");
 * Map<String, String> env = pb.environment();
 * env.put("VAR1", "myValue");
 * env.remove("OTHERVAR");
 * env.put("VAR2", env.get("VAR1") + "suffix");
 * pb.directory(new File("myDir"));
 * File log = new File("log");
 * pb.redirectErrorStream(true);
 * pb.redirectOutput(Redirect.appendTo(log));
 * Process p = pb.start();
 * assert pb.redirectInput() == Redirect.PIPE;
 * assert pb.redirectOutput().file() == log;
 * assert p.getInputStream().read() == -1;
 * }</pre>
 *
 * <p>To start a process with an explicit set of environment
 * variables, first call {@link java.util.Map#clear() Map.clear()}
 * before adding environment variables.
 *
 * @author Martin Buchholz
 * @since 1.5
 */

public final class ProcessBuilder {

  private List<String> command;
  private File directory;
  private Map<String, String> environment;
  private boolean redirectErrorStream;
  private Redirect[] redirects;

  /**
   * Constructs a process builder with the specified operating
   * system program and arguments.  This constructor does <i>not</i>
   * make a copy of the {@code command} list.  Subsequent
   * updates to the list will be reflected in the state of the
   * process builder.  It is not checked whether
   * {@code command} corresponds to a valid operating system
   * command.
   *
   * @param command the list containing the program and its arguments
   * @throws NullPointerException if the argument is null
   */
  public ProcessBuilder(List<String> command) {
    if (command == null) {
      throw new NullPointerException();
    }
    this.command = command;
  }

  /**
   * Constructs a process builder with the specified operating
   * system program and arguments.  This is a convenience
   * constructor that sets the process builder's command to a string
   * list containing the same strings as the {@code command}
   * array, in the same order.  It is not checked whether
   * {@code command} corresponds to a valid operating system
   * command.
   *
   * @param command a string array containing the program and its arguments
   */
  public ProcessBuilder(String... command) {
    this.command = new ArrayList<>(command.length);
    for (String arg : command) {
      this.command.add(arg);
    }
  }

  /**
   * Sets this process builder's operating system program and
   * arguments.  This method does <i>not</i> make a copy of the
   * {@code command} list.  Subsequent updates to the list will
   * be reflected in the state of the process builder.  It is not
   * checked whether {@code command} corresponds to a valid
   * operating system command.
   *
   * @param command the list containing the program and its arguments
   * @return this process builder
   * @throws NullPointerException if the argument is null
   */
  public ProcessBuilder command(List<String> command) {
    if (command == null) {
      throw new NullPointerException();
    }
    this.command = command;
    return this;
  }

  /**
   * Sets this process builder's operating system program and
   * arguments.  This is a convenience method that sets the command
   * to a string list containing the same strings as the
   * {@code command} array, in the same order.  It is not
   * checked whether {@code command} corresponds to a valid
   * operating system command.
   *
   * @param command a string array containing the program and its arguments
   * @return this process builder
   */
  public ProcessBuilder command(String... command) {
    this.command = new ArrayList<>(command.length);
    for (String arg : command) {
      this.command.add(arg);
    }
    return this;
  }

  /**
   * Returns this process builder's operating system program and
   * arguments.  The returned list is <i>not</i> a copy.  Subsequent
   * updates to the list will be reflected in the state of this
   * process builder.
   *
   * @return this process builder's program and its arguments
   */
  public List<String> command() {
    return command;
  }

  /**
   * Returns a string map view of this process builder's environment.
   *
   * Whenever a process builder is created, the environment is
   * initialized to a copy of the current process environment (see
   * {@link System#getenv()}).  Subprocesses subsequently started by
   * this object's {@link #start()} method will use this map as
   * their environment.
   *
   * <p>The returned object may be modified using ordinary {@link
   * java.util.Map Map} operations.  These modifications will be
   * visible to subprocesses started via the {@link #start()}
   * method.  Two {@code ProcessBuilder} instances always
   * contain independent process environments, so changes to the
   * returned map will never be reflected in any other
   * {@code ProcessBuilder} instance or the values returned by
   * {@link System#getenv System.getenv}.
   *
   * <p>If the system does not support environment variables, an
   * empty map is returned.
   *
   * <p>The returned map does not permit null keys or values.
   * Attempting to insert or query the presence of a null key or
   * value will throw a {@link NullPointerException}.
   * Attempting to query the presence of a key or value which is not
   * of type {@link String} will throw a {@link ClassCastException}.
   *
   * <p>The behavior of the returned map is system-dependent.  A
   * system may not allow modifications to environment variables or
   * may forbid certain variable names or values.  For this reason,
   * attempts to modify the map may fail with
   * {@link UnsupportedOperationException} or
   * {@link IllegalArgumentException}
   * if the modification is not permitted by the operating system.
   *
   * <p>Since the external format of environment variable names and
   * values is system-dependent, there may not be a one-to-one
   * mapping between them and Java's Unicode strings.  Nevertheless,
   * the map is implemented in such a way that environment variables
   * which are not modified by Java code will have an unmodified
   * native representation in the subprocess.
   *
   * <p>The returned map and its collection views may not obey the
   * general contract of the {@link Object#equals} and
   * {@link Object#hashCode} methods.
   *
   * <p>The returned map is typically case-sensitive on all platforms.
   *
   * <p>If a security manager exists, its
   * {@link SecurityManager#checkPermission checkPermission} method
   * is called with a
   * {@link RuntimePermission}{@code ("getenv.*")} permission.
   * This may result in a {@link SecurityException} being thrown.
   *
   * <p>When passing information to a Java subprocess,
   * <a href=System.html#EnvironmentVSSystemProperties>system properties</a>
   * are generally preferred over environment variables.
   *
   * @return this process builder's environment
   * @throws SecurityException if a security manager exists and its {@link
   * SecurityManager#checkPermission checkPermission} method doesn't allow access to the process
   * environment
   * @see Runtime#exec(String[], String[], java.io.File)
   * @see System#getenv()
   */
  public Map<String, String> environment() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkPermission(new RuntimePermission("getenv.*"));
    }

    if (environment == null) {
      environment = ProcessEnvironment.environment();
    }

    assert environment != null;

    return environment;
  }

  // Only for use by Runtime.exec(...envp...)
  ProcessBuilder environment(String[] envp) {
    assert environment == null;
    if (envp != null) {
      environment = ProcessEnvironment.emptyEnvironment(envp.length);
      assert environment != null;

      for (String envstring : envp) {
        // Before 1.5, we blindly passed invalid envstrings
        // to the child process.
        // We would like to throw an exception, but do not,
        // for compatibility with old broken code.

        // Silently discard any trailing junk.
        if (envstring.indexOf((int) '\u0000') != -1) {
          envstring = envstring.replaceFirst("\u0000.*", "");
        }

        int eqlsign =
            envstring.indexOf('=', ProcessEnvironment.MIN_NAME_LENGTH);
        // Silently ignore envstrings lacking the required `='.
        if (eqlsign != -1) {
          environment.put(envstring.substring(0, eqlsign),
              envstring.substring(eqlsign + 1));
        }
      }
    }
    return this;
  }

  /**
   * Returns this process builder's working directory.
   *
   * Subprocesses subsequently started by this object's {@link
   * #start()} method will use this as their working directory.
   * The returned value may be {@code null} -- this means to use
   * the working directory of the current Java process, usually the
   * directory named by the system property {@code user.dir},
   * as the working directory of the child process.
   *
   * @return this process builder's working directory
   */
  public File directory() {
    return directory;
  }

  /**
   * Sets this process builder's working directory.
   *
   * Subprocesses subsequently started by this object's {@link
   * #start()} method will use this as their working directory.
   * The argument may be {@code null} -- this means to use the
   * working directory of the current Java process, usually the
   * directory named by the system property {@code user.dir},
   * as the working directory of the child process.
   *
   * @param directory the new working directory
   * @return this process builder
   */
  public ProcessBuilder directory(File directory) {
    this.directory = directory;
    return this;
  }

  // ---------------- I/O Redirection ----------------

  /**
   * Implements a <a href="#redirect-output">null input stream</a>.
   */
  static class NullInputStream extends InputStream {

    static final NullInputStream INSTANCE = new NullInputStream();

    private NullInputStream() {
    }

    public int read() {
      return -1;
    }

    public int available() {
      return 0;
    }
  }

  /**
   * Implements a <a href="#redirect-input">null output stream</a>.
   */
  static class NullOutputStream extends OutputStream {

    static final NullOutputStream INSTANCE = new NullOutputStream();

    private NullOutputStream() {
    }

    public void write(int b) throws IOException {
      throw new IOException("Stream closed");
    }
  }

  /**
   * Represents a source of subprocess input or a destination of
   * subprocess output.
   *
   * Each {@code Redirect} instance is one of the following:
   *
   * <ul>
   * <li>the special value {@link #PIPE Redirect.PIPE}
   * <li>the special value {@link #INHERIT Redirect.INHERIT}
   * <li>a redirection to read from a file, created by an invocation of
   * {@link Redirect#from Redirect.from(File)}
   * <li>a redirection to write to a file,  created by an invocation of
   * {@link Redirect#to Redirect.to(File)}
   * <li>a redirection to append to a file, created by an invocation of
   * {@link Redirect#appendTo Redirect.appendTo(File)}
   * </ul>
   *
   * <p>Each of the above categories has an associated unique
   * {@link Type Type}.
   *
   * @since 1.7
   */
  public static abstract class Redirect {

    /**
     * The type of a {@link Redirect}.
     */
    public enum Type {
      /**
       * The type of {@link Redirect#PIPE Redirect.PIPE}.
       */
      PIPE,

      /**
       * The type of {@link Redirect#INHERIT Redirect.INHERIT}.
       */
      INHERIT,

      /**
       * The type of redirects returned from
       * {@link Redirect#from Redirect.from(File)}.
       */
      READ,

      /**
       * The type of redirects returned from
       * {@link Redirect#to Redirect.to(File)}.
       */
      WRITE,

      /**
       * The type of redirects returned from
       * {@link Redirect#appendTo Redirect.appendTo(File)}.
       */
      APPEND
    }

    ;

    /**
     * Returns the type of this {@code Redirect}.
     *
     * @return the type of this {@code Redirect}
     */
    public abstract Type type();

    /**
     * Indicates that subprocess I/O will be connected to the
     * current Java process over a pipe.
     *
     * This is the default handling of subprocess standard I/O.
     *
     * <p>It will always be true that
     * <pre> {@code
     * Redirect.PIPE.file() == null &&
     * Redirect.PIPE.type() == Redirect.Type.PIPE
     * }</pre>
     */
    public static final Redirect PIPE = new Redirect() {
      public Type type() {
        return Type.PIPE;
      }

      public String toString() {
        return type().toString();
      }
    };

    /**
     * Indicates that subprocess I/O source or destination will be the
     * same as those of the current process.  This is the normal
     * behavior of most operating system command interpreters (shells).
     *
     * <p>It will always be true that
     * <pre> {@code
     * Redirect.INHERIT.file() == null &&
     * Redirect.INHERIT.type() == Redirect.Type.INHERIT
     * }</pre>
     */
    public static final Redirect INHERIT = new Redirect() {
      public Type type() {
        return Type.INHERIT;
      }

      public String toString() {
        return type().toString();
      }
    };

    /**
     * Returns the {@link File} source or destination associated
     * with this redirect, or {@code null} if there is no such file.
     *
     * @return the file associated with this redirect, or {@code null} if there is no such file
     */
    public File file() {
      return null;
    }

    /**
     * When redirected to a destination file, indicates if the output
     * is to be written to the end of the file.
     */
    boolean append() {
      throw new UnsupportedOperationException();
    }

    /**
     * Returns a redirect to read from the specified file.
     *
     * <p>It will always be true that
     * <pre> {@code
     * Redirect.from(file).file() == file &&
     * Redirect.from(file).type() == Redirect.Type.READ
     * }</pre>
     *
     * @param file The {@code File} for the {@code Redirect}.
     * @return a redirect to read from the specified file
     * @throws NullPointerException if the specified file is null
     */
    public static Redirect from(final File file) {
      if (file == null) {
        throw new NullPointerException();
      }
      return new Redirect() {
        public Type type() {
          return Type.READ;
        }

        public File file() {
          return file;
        }

        public String toString() {
          return "redirect to read from file \"" + file + "\"";
        }
      };
    }

    /**
     * Returns a redirect to write to the specified file.
     * If the specified file exists when the subprocess is started,
     * its previous contents will be discarded.
     *
     * <p>It will always be true that
     * <pre> {@code
     * Redirect.to(file).file() == file &&
     * Redirect.to(file).type() == Redirect.Type.WRITE
     * }</pre>
     *
     * @param file The {@code File} for the {@code Redirect}.
     * @return a redirect to write to the specified file
     * @throws NullPointerException if the specified file is null
     */
    public static Redirect to(final File file) {
      if (file == null) {
        throw new NullPointerException();
      }
      return new Redirect() {
        public Type type() {
          return Type.WRITE;
        }

        public File file() {
          return file;
        }

        public String toString() {
          return "redirect to write to file \"" + file + "\"";
        }

        boolean append() {
          return false;
        }
      };
    }

    /**
     * Returns a redirect to append to the specified file.
     * Each write operation first advances the position to the
     * end of the file and then writes the requested data.
     * Whether the advancement of the position and the writing
     * of the data are done in a single atomic operation is
     * system-dependent and therefore unspecified.
     *
     * <p>It will always be true that
     * <pre> {@code
     * Redirect.appendTo(file).file() == file &&
     * Redirect.appendTo(file).type() == Redirect.Type.APPEND
     * }</pre>
     *
     * @param file The {@code File} for the {@code Redirect}.
     * @return a redirect to append to the specified file
     * @throws NullPointerException if the specified file is null
     */
    public static Redirect appendTo(final File file) {
      if (file == null) {
        throw new NullPointerException();
      }
      return new Redirect() {
        public Type type() {
          return Type.APPEND;
        }

        public File file() {
          return file;
        }

        public String toString() {
          return "redirect to append to file \"" + file + "\"";
        }

        boolean append() {
          return true;
        }
      };
    }

    /**
     * Compares the specified object with this {@code Redirect} for
     * equality.  Returns {@code true} if and only if the two
     * objects are identical or both objects are {@code Redirect}
     * instances of the same type associated with non-null equal
     * {@code File} instances.
     */
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      }
      if (!(obj instanceof Redirect)) {
        return false;
      }
      Redirect r = (Redirect) obj;
      if (r.type() != this.type()) {
        return false;
      }
      assert this.file() != null;
      return this.file().equals(r.file());
    }

    /**
     * Returns a hash code value for this {@code Redirect}.
     *
     * @return a hash code value for this {@code Redirect}
     */
    public int hashCode() {
      File file = file();
      if (file == null) {
        return super.hashCode();
      } else {
        return file.hashCode();
      }
    }

    /**
     * No public constructors.  Clients must use predefined
     * static {@code Redirect} instances or factory methods.
     */
    private Redirect() {
    }
  }

  private Redirect[] redirects() {
    if (redirects == null) {
      redirects = new Redirect[]{
          Redirect.PIPE, Redirect.PIPE, Redirect.PIPE
      };
    }
    return redirects;
  }

  /**
   * Sets this process builder's standard input source.
   *
   * Subprocesses subsequently started by this object's {@link #start()}
   * method obtain their standard input from this source.
   *
   * <p>If the source is {@link Redirect#PIPE Redirect.PIPE}
   * (the initial value), then the standard input of a
   * subprocess can be written to using the output stream
   * returned by {@link Process#getOutputStream()}.
   * If the source is set to any other value, then
   * {@link Process#getOutputStream()} will return a
   * <a href="#redirect-input">null output stream</a>.
   *
   * @param source the new standard input source
   * @return this process builder
   * @throws IllegalArgumentException if the redirect does not correspond to a valid source of data,
   * that is, has type {@link Redirect.Type#WRITE WRITE} or {@link Redirect.Type#APPEND APPEND}
   * @since 1.7
   */
  public ProcessBuilder redirectInput(Redirect source) {
    if (source.type() == Redirect.Type.WRITE ||
        source.type() == Redirect.Type.APPEND) {
      throw new IllegalArgumentException(
          "Redirect invalid for reading: " + source);
    }
    redirects()[0] = source;
    return this;
  }

  /**
   * Sets this process builder's standard output destination.
   *
   * Subprocesses subsequently started by this object's {@link #start()}
   * method send their standard output to this destination.
   *
   * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
   * (the initial value), then the standard output of a subprocess
   * can be read using the input stream returned by {@link
   * Process#getInputStream()}.
   * If the destination is set to any other value, then
   * {@link Process#getInputStream()} will return a
   * <a href="#redirect-output">null input stream</a>.
   *
   * @param destination the new standard output destination
   * @return this process builder
   * @throws IllegalArgumentException if the redirect does not correspond to a valid destination of
   * data, that is, has type {@link Redirect.Type#READ READ}
   * @since 1.7
   */
  public ProcessBuilder redirectOutput(Redirect destination) {
    if (destination.type() == Redirect.Type.READ) {
      throw new IllegalArgumentException(
          "Redirect invalid for writing: " + destination);
    }
    redirects()[1] = destination;
    return this;
  }

  /**
   * Sets this process builder's standard error destination.
   *
   * Subprocesses subsequently started by this object's {@link #start()}
   * method send their standard error to this destination.
   *
   * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
   * (the initial value), then the error output of a subprocess
   * can be read using the input stream returned by {@link
   * Process#getErrorStream()}.
   * If the destination is set to any other value, then
   * {@link Process#getErrorStream()} will return a
   * <a href="#redirect-output">null input stream</a>.
   *
   * <p>If the {@link #redirectErrorStream redirectErrorStream}
   * attribute has been set {@code true}, then the redirection set
   * by this method has no effect.
   *
   * @param destination the new standard error destination
   * @return this process builder
   * @throws IllegalArgumentException if the redirect does not correspond to a valid destination of
   * data, that is, has type {@link Redirect.Type#READ READ}
   * @since 1.7
   */
  public ProcessBuilder redirectError(Redirect destination) {
    if (destination.type() == Redirect.Type.READ) {
      throw new IllegalArgumentException(
          "Redirect invalid for writing: " + destination);
    }
    redirects()[2] = destination;
    return this;
  }

  /**
   * Sets this process builder's standard input source to a file.
   *
   * <p>This is a convenience method.  An invocation of the form
   * {@code redirectInput(file)}
   * behaves in exactly the same way as the invocation
   * {@link #redirectInput(Redirect) redirectInput}
   * {@code (Redirect.from(file))}.
   *
   * @param file the new standard input source
   * @return this process builder
   * @since 1.7
   */
  public ProcessBuilder redirectInput(File file) {
    return redirectInput(Redirect.from(file));
  }

  /**
   * Sets this process builder's standard output destination to a file.
   *
   * <p>This is a convenience method.  An invocation of the form
   * {@code redirectOutput(file)}
   * behaves in exactly the same way as the invocation
   * {@link #redirectOutput(Redirect) redirectOutput}
   * {@code (Redirect.to(file))}.
   *
   * @param file the new standard output destination
   * @return this process builder
   * @since 1.7
   */
  public ProcessBuilder redirectOutput(File file) {
    return redirectOutput(Redirect.to(file));
  }

  /**
   * Sets this process builder's standard error destination to a file.
   *
   * <p>This is a convenience method.  An invocation of the form
   * {@code redirectError(file)}
   * behaves in exactly the same way as the invocation
   * {@link #redirectError(Redirect) redirectError}
   * {@code (Redirect.to(file))}.
   *
   * @param file the new standard error destination
   * @return this process builder
   * @since 1.7
   */
  public ProcessBuilder redirectError(File file) {
    return redirectError(Redirect.to(file));
  }

  /**
   * Returns this process builder's standard input source.
   *
   * Subprocesses subsequently started by this object's {@link #start()}
   * method obtain their standard input from this source.
   * The initial value is {@link Redirect#PIPE Redirect.PIPE}.
   *
   * @return this process builder's standard input source
   * @since 1.7
   */
  public Redirect redirectInput() {
    return (redirects == null) ? Redirect.PIPE : redirects[0];
  }

  /**
   * Returns this process builder's standard output destination.
   *
   * Subprocesses subsequently started by this object's {@link #start()}
   * method redirect their standard output to this destination.
   * The initial value is {@link Redirect#PIPE Redirect.PIPE}.
   *
   * @return this process builder's standard output destination
   * @since 1.7
   */
  public Redirect redirectOutput() {
    return (redirects == null) ? Redirect.PIPE : redirects[1];
  }

  /**
   * Returns this process builder's standard error destination.
   *
   * Subprocesses subsequently started by this object's {@link #start()}
   * method redirect their standard error to this destination.
   * The initial value is {@link Redirect#PIPE Redirect.PIPE}.
   *
   * @return this process builder's standard error destination
   * @since 1.7
   */
  public Redirect redirectError() {
    return (redirects == null) ? Redirect.PIPE : redirects[2];
  }

  /**
   * Sets the source and destination for subprocess standard I/O
   * to be the same as those of the current Java process.
   *
   * <p>This is a convenience method.  An invocation of the form
   * <pre> {@code
   * pb.inheritIO()
   * }</pre>
   * behaves in exactly the same way as the invocation
   * <pre> {@code
   * pb.redirectInput(Redirect.INHERIT)
   *   .redirectOutput(Redirect.INHERIT)
   *   .redirectError(Redirect.INHERIT)
   * }</pre>
   *
   * This gives behavior equivalent to most operating system
   * command interpreters, or the standard C library function
   * {@code system()}.
   *
   * @return this process builder
   * @since 1.7
   */
  public ProcessBuilder inheritIO() {
    Arrays.fill(redirects(), Redirect.INHERIT);
    return this;
  }

  /**
   * Tells whether this process builder merges standard error and
   * standard output.
   *
   * <p>If this property is {@code true}, then any error output
   * generated by subprocesses subsequently started by this object's
   * {@link #start()} method will be merged with the standard
   * output, so that both can be read using the
   * {@link Process#getInputStream()} method.  This makes it easier
   * to correlate error messages with the corresponding output.
   * The initial value is {@code false}.
   *
   * @return this process builder's {@code redirectErrorStream} property
   */
  public boolean redirectErrorStream() {
    return redirectErrorStream;
  }

  /**
   * Sets this process builder's {@code redirectErrorStream} property.
   *
   * <p>If this property is {@code true}, then any error output
   * generated by subprocesses subsequently started by this object's
   * {@link #start()} method will be merged with the standard
   * output, so that both can be read using the
   * {@link Process#getInputStream()} method.  This makes it easier
   * to correlate error messages with the corresponding output.
   * The initial value is {@code false}.
   *
   * @param redirectErrorStream the new property value
   * @return this process builder
   */
  public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
    this.redirectErrorStream = redirectErrorStream;
    return this;
  }

  /**
   * Starts a new process using the attributes of this process builder.
   *
   * <p>The new process will
   * invoke the command and arguments given by {@link #command()},
   * in a working directory as given by {@link #directory()},
   * with a process environment as given by {@link #environment()}.
   *
   * <p>This method checks that the command is a valid operating
   * system command.  Which commands are valid is system-dependent,
   * but at the very least the command must be a non-empty list of
   * non-null strings.
   *
   * <p>A minimal set of system dependent environment variables may
   * be required to start a process on some operating systems.
   * As a result, the subprocess may inherit additional environment variable
   * settings beyond those in the process builder's {@link #environment()}.
   *
   * <p>If there is a security manager, its
   * {@link SecurityManager#checkExec checkExec}
   * method is called with the first component of this object's
   * {@code command} array as its argument. This may result in
   * a {@link SecurityException} being thrown.
   *
   * <p>Starting an operating system process is highly system-dependent.
   * Among the many things that can go wrong are:
   * <ul>
   * <li>The operating system program file was not found.
   * <li>Access to the program file was denied.
   * <li>The working directory does not exist.
   * </ul>
   *
   * <p>In such cases an exception will be thrown.  The exact nature
   * of the exception is system-dependent, but it will always be a
   * subclass of {@link IOException}.
   *
   * <p>Subsequent modifications to this process builder will not
   * affect the returned {@link Process}.
   *
   * @return a new {@link Process} object for managing the subprocess
   * @throws NullPointerException if an element of the command list is null
   * @throws IndexOutOfBoundsException if the command is an empty list (has size {@code 0})
   * @throws SecurityException if a security manager exists and <ul>
   *
   * <li>its {@link SecurityManager#checkExec checkExec} method doesn't allow creation of the
   * subprocess, or
   *
   * <li>the standard input to the subprocess was {@linkplain #redirectInput redirected from a file}
   * and the security manager's {@link SecurityManager#checkRead checkRead} method denies read
   * access to the file, or
   *
   * <li>the standard output or standard error of the subprocess was {@linkplain #redirectOutput
   * redirected to a file} and the security manager's {@link SecurityManager#checkWrite checkWrite}
   * method denies write access to the file
   *
   * </ul>
   * @throws IOException if an I/O error occurs
   * @see Runtime#exec(String[], String[], java.io.File)
   */
  public Process start() throws IOException {
    // Must convert to array first -- a malicious user-supplied
    // list might try to circumvent the security check.
    String[] cmdarray = command.toArray(new String[command.size()]);
    cmdarray = cmdarray.clone();

    for (String arg : cmdarray) {
      if (arg == null) {
        throw new NullPointerException();
      }
    }
    // Throws IndexOutOfBoundsException if command is empty
    String prog = cmdarray[0];

    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkExec(prog);
    }

    String dir = directory == null ? null : directory.toString();

    for (int i = 1; i < cmdarray.length; i++) {
      if (cmdarray[i].indexOf('\u0000') >= 0) {
        throw new IOException("invalid null character in command");
      }
    }

    try {
      return ProcessImpl.start(cmdarray,
          environment,
          dir,
          redirects,
          redirectErrorStream);
    } catch (IOException | IllegalArgumentException e) {
      String exceptionInfo = ": " + e.getMessage();
      Throwable cause = e;
      if ((e instanceof IOException) && security != null) {
        // Can not disclose the fail reason for read-protected files.
        try {
          security.checkRead(prog);
        } catch (SecurityException se) {
          exceptionInfo = "";
          cause = se;
        }
      }
      // It's much easier for us to create a high-quality error
      // message than the low-level C code which found the problem.
      throw new IOException(
          "Cannot run program \"" + prog + "\""
              + (dir == null ? "" : " (in directory \"" + dir + "\")")
              + exceptionInfo,
          cause);
    }
  }
}
